package com.nineshadow.games.netty.message;

import cn.stylefeng.roses.core.util.SpringContextHolder;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.nineshadow.games.launch.GameWorld;
import com.nineshadow.games.launch.HallServer;
import com.nineshadow.games.netty.socket.Message;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class MessageSend {


    /**
     * socket 心跳包内容
     */
    private static final String ServerRecv = "@";
    private static final String ServerPost = "#";

    /**
     * websocket 心跳包内容
     */
    private static final String Web_ServerRecv = "+";
    private static final String Web_ServerPost = "-";


    /**
     * 消息发送处理
     *
     * @param ctx
     * @param msg
     */
    public static void sendMessage(ChannelHandlerContext ctx, Object msg) throws Exception {
        //socket 请求
        String receiveMessage = "";
        byte[] receiveData = null;
        if (msg instanceof Message) {
            //log.info("socket 消息处理");
            Message message = (Message) msg;
            if (message.getType() == 99) {
                //消息如果是心跳，则心跳处理
                String temp = new String(message.getContent(), "UTF-8");
                if (temp.equals(ServerRecv)) {
                    handleHeartbreat(ctx, true);
                }
                return;
            } else if (message.getType() == 0) {
                //数据处理
                receiveMessage = new String(message.getContent(), "UTF-8");
                receiveData = ((Message) message).getData();
                standardMessage(ctx, receiveMessage, receiveData);
            } else {
                return;
            }
        } else if (msg instanceof String) {
            //log.info("websocket 消息处理");
            String message = (String) msg;
            if (message.equals(Web_ServerRecv)) {
                handleHeartbreat(ctx, false);
            } else {
                //数据处理
                standardMessage(ctx, message, receiveData);
            }
        }

    }


    /**
     * 处理心跳包
     *
     * @param ctx
     * @param isSocket true socket false websocket
     * @throws Exception
     */
    private static void handleHeartbreat(ChannelHandlerContext ctx, boolean isSocket) throws Exception {
        /** 返回心跳预设语句 */
        Message obj = new Message();
        if (isSocket) {
            obj.setContent(ServerPost.getBytes(Charset.forName("utf-8")));
        } else {
            obj.setContent(Web_ServerPost.getBytes(Charset.forName("utf-8")));
        }
        obj.setTimestamp(System.currentTimeMillis());
        obj.setType((short) 99);
        obj.setData(new byte[0]);
        ctx.channel().writeAndFlush(obj);
    }

    /**
     * 心跳超时处理
     *
     * @param ctx
     * @param evt
     * @param unRecPingTimes 失败计数器
     */
    public static int TimeIdleStateEvent(ChannelHandlerContext ctx, Object evt, int unRecPingTimes) throws Exception {
        IdleStateEvent event = (IdleStateEvent) evt;
        String type = "";
        if (event.state() == IdleState.READER_IDLE) {
            type = "读操作超时,超时次数" + unRecPingTimes;
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            log.info("id:" + userId + "," + ctx.channel().remoteAddress() + "超时类型：" + type);
            // 失败计数器次数大于等于3次的时候，关闭链接，等待client重连
            if (unRecPingTimes >= 3) {
                // 连续超过N次未收到client的ping消息，那么关闭该通道，等待client重连
                try {
                    log.info(userId + "执行掉线操作");
                    HallServer.offline(ctx);
                } catch (Exception e) {
                    log.info("断线操作异常");
                    log.error(e.getMessage(),e);
                }
                ctx.channel().close();
            } else {
                // 失败计数器加1
                unRecPingTimes++;
            }
        } else if (event.state() == IdleState.WRITER_IDLE) {
            //type = "写操作超市";
        } else if (event.state() == IdleState.ALL_IDLE) {
            //type = "全部操作超时";
        }
        return unRecPingTimes;
    }


    private static void standardMessage(ChannelHandlerContext ctx, String receiveMessage, byte[] receiveData) throws Exception {
        // 解析调用
        String URL = "";
        Object[] args = null;
        String beanId = "";
        String method = "";
        try {
            while (receiveMessage.toCharArray()[0] != '{') {
                receiveMessage = receiveMessage.substring(1);
            }

            if (receiveMessage.contains("&_")) {
                receiveMessage = receiveMessage.replaceAll("&_", "");
            }

            log.info("receiveMessage:" + receiveMessage);
            JSONObject jo = JSON.parseObject(receiveMessage);
            URL = jo.getString("m");
            String[] urls = URL.split("/");
            if (urls.length == 2) {
                beanId = urls[0];
                method = urls[1];
            }
            if (jo.getJSONArray("a") != null) {
                args = jo.getJSONArray("a").toArray();
            } else {
                args = new Object[]{};
            }
        } catch (Exception e) {
            //log.info("请求参数无效!:" + receiveMessage);
            send(ctx, "无", "请求参数无效!:" + receiveMessage, false);
            Map<String, Object> result = new HashMap<>();
            result.put("success", 0);
            result.put("message", "请求参数无效!:" + receiveMessage);
            send(ctx, URL, result, true);
            return;
        }

        // 验证是否登录
        if (!GameWorld.getSessionMap().containsKey(ctx.channel().id().asLongText()) && !GameWorld.containsURL(URL)) {
            log.info("未登录操作:" + URL + " mgs:" + receiveMessage);
            Map<String, Object> result = new HashMap<>();
            result.put("success", 0);
            result.put("message", "请先登陆游戏！");
            send(ctx, URL, result, true);
            return;
        }

        log.info("收到数据,    ID:" + ctx.channel().id() + " service:[ " + beanId + " ]    method:[ " + method + " ]   args:[ " + Arrays.toString(args) + " ]");
        // 在最后增加IoSession参数
        //若无数据则不显示数据参数
        if (receiveData != null && receiveData.length > 0) {
            Object[] argsTemp = new Object[args.length + 2];
            argsTemp[0] = ctx;
            argsTemp[1] = receiveData;
            for (int i = 0; i < args.length; i++) {
                argsTemp[i + 2] = args[i];
            }
            args = argsTemp;
        } else {
            Object[] argsTemp = new Object[args.length + 1];
            argsTemp[0] = ctx;
            for (int i = 0; i < args.length; i++) {
                argsTemp[i + 1] = args[i];
            }
            args = argsTemp;
        }

        Object service = SpringContextHolder.getBean(beanId);
        Method m = service.getClass().getDeclaredMethod(method, getAndSetClasses(args));
        Object result = m.invoke(service, args);
        if (result != null) {
            if (result instanceof Map<?, ?> || result instanceof String) {
                if (result instanceof String) {
                    send(ctx, URL, JSONObject.parse((String) result), true);
                } else {
                    send(ctx, URL, result, true);
                }
            } else if (result instanceof Result) {
                send(ctx, URL, ((Result) result).getMap(), true, ((Result) result).getData());
            }
        }
    }


    private static Class<?>[] getAndSetClasses(final Object[] args) {
        try {
            Class<?>[] classArray = new Class<?>[args.length];

            for (int i = 0; i < args.length; i++) {
                classArray[i] = args[i].getClass();

                if (args[i] instanceof Integer) {
                    classArray[i] = Integer.TYPE;
                    args[i] = (int) args[i];
                    continue;
                } else if (args[i] instanceof Boolean) {
                    classArray[i] = Boolean.TYPE;
                    args[i] = (boolean) args[i];
                    continue;
                } else if (args[i] instanceof Map) {
                    classArray[i] = Map.class;
                    continue;
                } else if (args[i] instanceof Long) {
                    classArray[i] = Long.TYPE;
                    args[i] = (long) args[i];
                    continue;
                } else if (args[i] instanceof Double) {
                    classArray[i] = Double.TYPE;
                    args[i] = (double) args[i];
                    continue;
                } else if (args[i] instanceof ChannelHandlerContext) {
                    classArray[i] = ChannelHandlerContext.class;
                    continue;
                } else if (args[i] instanceof Float) {
                    classArray[i] = Float.TYPE;
                    args[i] = (float) args[i];
                } else if (args[i] instanceof int[]) {
                    classArray[i] = int[].class;
                    args[i] = args[i];
                }
            }
            return classArray;
        } catch (Exception e) {
            log.error("getAndSetClasses异常", e);
        }
        return null;
    }

    /**
     * 正常发送消息
     *
     * @param ctx
     * @param method 方法名
     * @param args   消息内容
     * @param isBack true 回调 false 推送
     */
    public static void send(final ChannelHandlerContext ctx, final String method, final Object args, boolean isBack) {
        if (ctx != null) {
            ctx.channel().eventLoop().execute(new JsonSendTask(ctx, method, args, isBack));
        }
    }


    /**
     * 延迟发送消息
     *
     * @param ctx
     * @param method
     * @param args
     * @param isBack
     * @param delay  延时时间
     * @param time
     */
    public static void sendWithDelay(final ChannelHandlerContext ctx, final String method, final Object args, boolean isBack, long delay, TimeUnit time) {
        if (ctx != null) {
            Runnable runnable = new JsonSendTask(ctx, method, args, isBack);
            ctx.channel().eventLoop().schedule(runnable, delay, time);
        }
    }

    /**
     * 发送含有回掉信息的消息
     *
     * @param ctx
     * @param method
     * @param args
     * @param isBack
     * @return
     */
    public static Future<Integer> sendWithReturn(final ChannelHandlerContext ctx, final String method, final Object args, boolean isBack) {
        if (ctx != null) {
            Callable callable = new JsonSendTask(ctx, method, args, isBack);
            Future future = ctx.channel().eventLoop().submit(callable);
            return future;
        } else {
            return null;
        }
    }

    /**
     * 发送回掉信息条件满足的消息
     *
     * @param ctx
     * @param method
     * @param args
     * @param isBack
     * @param future
     */
    public static void sendAfterFuture(final ChannelHandlerContext ctx, final String method, final Object args, boolean isBack, FutureTask<Integer> future) {
        if (ctx != null) {
            ctx.channel().eventLoop().execute(new JsonSendTask(ctx, method, args, isBack, future));
        }
    }

    /**
     * 发送带有二进制文件消息
     *
     * @param ctx
     * @param method
     * @param args
     * @param isBack
     * @param data
     */
    public static void send(final ChannelHandlerContext ctx, final String method, final Object args, boolean isBack, byte[] data) {
        if (ctx != null) {
            ctx.channel().eventLoop().execute(new JsonSendTask(ctx, method, args, isBack, data));
        }
    }
}
